# Copyright owner: Chris Beaven (http://smileychris.tactful.co.nz)
# Licensed under MIT

import httplib, urllib
from django.forms import *
from django.conf import settings
from django.utils.safestring import mark_safe

OPTIONS_SCRIPT_HTML = u'''<script type="text/javascript">
   var RecaptchaOptions = %r;
</script>
'''
RECAPTCHA_HTML = u'''%(options)s<script type="text/javascript" src="http://api.recaptcha.net/challenge?k=%(public_key)s"></script>
<noscript>
   <iframe src="http://api.recaptcha.net/noscript?k=%(public_key)s"
       height="300" width="500" frameborder="0"></iframe><br />
   <textarea name="recaptcha_challenge_field" rows="3" cols="40">
   </textarea>
   <input type="hidden" name="recaptcha_response_field" value="manual_challenge" />
</noscript>'''


class RecaptchaWidget(Widget):
    def __init__(self, theme=None, tabindex=None):
        '''
        From http://recaptcha.net/apidocs/captcha/client.html#look-n-feel:

        theme:      'red' | 'white' | 'blackglass' | 'clean'

            Defines which theme to use for reCAPTCHA.

        tabindex:   any integer

            Sets a tabindex for the reCAPTCHA text box. If other elements in
            the form use a tabindex, this should be set so that navigation is
            easier for the user.
        '''
        options = {}
        if theme:
            options['theme'] = theme
        if tabindex:
            options['tabindex'] = tabindex
        self.options = options
        super(RecaptchaWidget, self).__init__()

    def render(self, name, value, attrs=None):
        args = dict(public_key=settings.RECAPTCHA_PUBLIC_KEY)
        if self.options:
            args['options'] = OPTIONS_SCRIPT_HTML % self.options
        return mark_safe(RECAPTCHA_HTML % args)

    def value_from_datadict(self, data, files, name):
        challenge = data.get('recaptcha_challenge_field')
        response = data.get('recaptcha_response_field')
        return (challenge, response)

    def id_for_label(self, id_):
        return None


class RecaptchaField(Field):
    widget = RecaptchaWidget
    default_error_messages = {
        'required': u'Please enter the CAPTCHA solution.',
        'invalid': u'An incorrect CAPTCHA solution was entered.',
        'no-remote-ip': u'CAPTCHA failed due to no visible IP address.',
        'challenge-error': u'An error occurred with the CAPTCHA service - try refreshing.',
        'unknown-error': u'The CAPTCHA service returned the following error: %(code)s.',
    }

    def __init__(self, *args, **kwargs):
        self.remote_ip = None
        super(RecaptchaField, self).__init__(*args, **kwargs)

    def clean(self, value):
        if not self.remote_ip:
            raise ValidationError(self.error_messages['no-remote-ip'])
        value = super(RecaptchaField, self).clean(value)
        challenge, response = value
        if not challenge:
            raise ValidationError(self.error_messages['challenge-error'])
        if not response:
            raise ValidationError(self.error_messages['required'])
        try:
            value = validate_recaptcha(self.remote_ip, challenge, response)
        except RecaptchaError, e:
            if e.code == 'incorrect-captcha-sol':
                raise ValidationError(self.error_messages['invalid'])
            raise ValidationError(self.error_messages['unknown-error'] % {'code': e.code})
        return value


class RecaptchaBaseForm(BaseForm):
    def __init__(self, remote_ip, *args, **kwargs):
        super(RecaptchaBaseForm, self).__init__(*args, **kwargs)
        for key, field in self.fields.items():
            if isinstance(field, RecaptchaField):
                field.remote_ip = remote_ip


class RecaptchaForm(RecaptchaBaseForm, Form):
    pass


class RecaptchaError(Exception):
    def __init__(self, code):
        self.code = code

    def __str__(self):
        return self.code


def validate_recaptcha(remote_ip, challenge, response):
    assert challenge, 'No challenge was provided for reCaptcha validation'
    # Request validation from recaptcha.net
    params = dict(privatekey=settings.RECAPTCHA_PRIVATE_KEY,
                  remoteip=remote_ip, challenge=challenge, response=response)
    params = urllib.urlencode(params)
    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
    conn = httplib.HTTPConnection("api-verify.recaptcha.net")
    conn.request("POST", "/verify", params, headers)
    response = conn.getresponse()
    if response.status == 200:
        data = response.read()
    else:
        data = ''
    conn.close()
    # Validate based on response data
    result = data.startswith('true')
    if not result:
        bits = data.split('\n', 2)
        error_code = ''
        if len(bits) > 1:
            error_code = bits[1]
        raise RecaptchaError(error_code)
    # Return dictionary
    return result
